---
title: "09-项目笔记(七) - API 开放项目项目教程 - 编程导航教程"
created: 2025-12-02
tags:
- 项目
aliases:
- 项目笔记(七) - API 开放项目项目教程 - 编程导航教程
---
# 项目笔记(七) - API 开放项目项目教程 - 编程导航教程
## 一、项目概述
本项目是一个面向开发者的 API 平台,提供 API 接口供开发者调用。用户通过注册登录,可以开通接口调用权限,并可以浏览和调用接口。每次调用都会进行统计,用户可以根据统计数据进行分析和优化。管理员可以发布接口、下线接口、接入接口,并可视化接口的调用情况和数据。本项目侧重于后端,涉及多种编程技巧和架构设计层面的知识。
## 二、本期时间点
![[2rhpNfIuHo8Xh0Ea-806b93e4.webp]]
## 三、本期计划(时间点 23:20-25:46)
1. 完成网关业务逻辑
2. 开发管理员分析的功能
3. 上线
## 四、后端网关业务(时间点 25:46-01:57:51)
### 1. 梳理网关业务逻辑(时间点 25:46-27:14)
**以下操作可以复用:**
1. 实际情况应该是去数据库中查是否已分配给用户秘钥(ak、sk是否合法)
2. 1. 先根据 accessKey 判断用户是否存在,查到 secretKey
2. 对比 secretKey 和用户传的加密后的 secretKey 是否一致
3. 从数据库中查询模拟接口是否存在,以及请求方法是否匹配(还可以校验请求参数)
4. 调用成功,接口调用次数 + 1 invokeCount
**进一步说明:**
找到 yuapi-gateway 项目的 CustomGlobalFilter,看下有哪些业务逻辑是没有做的。
目前的实现中,我们在鉴权时是直接写死 "yupi",检查 accessKey 是否与用户匹配。
然而,更好的实践是从数据库中查询用户分配的 accessKey 并验证其有效性。
![[1DM2zr6ZXhn4c6vR-c64cc580.webp]]
其次,就是我们要检查模拟接口是否存在,并验证请求方法是否匹配。
甚至还可以对用户的请求参数进行合法性校验。
![[O04QAyfj52PYe60I-8bdb0048.webp]]
再次,如果用户调用模拟接口成功,需要统计接口的调用。
![[Vnj4nysl8X5rwrXM-7ff1fdfb.webp]]
接下来我们来依次开发。
### 2. 项目启动及问题思考(时间点 27:14-31:04)
启动 nacos,进入 nacos 中的 bin 目录;
选中上面的路径栏,输入 cmd,按[Enter]回车进入。
![[UUdyC51GvvGRAS0W-6cc7fb23.webp]]
把命令粘贴进去,先以单机模式运行,在 8848 端口启动起来了。
```text
# Linux/Unix/Mac sh startup.sh -m standalone # ubuntu bash startup.sh -m standalone # Windows startup.cmd -m standalone
```
![[pmJXlbMdSPsgmQC7-cfb3ee7e.webp]]
启动 redis 之后,以 debug 模式启动 yuapi-backend 项目。
![[UG7FoIn15VNkMMyr-d71ea4d9.png]]
点击
![[pxJ2JYIaVw4qHukU-8d874c33.png]]按钮,可以看这个项目打的断点。
![[8c3DMqyjdjRO4Gur-aa206098.webp]]
把之前整的取消掉。
![[jFJAJvIwAKaAUtVY-4bea4367.webp]]
再以 debug 模式启动 yuapi-gateway 项目,有结果就行,报错没关系。
点击
![[pxJ2JYIaVw4qHukU-8d874c33.png]]按钮,看这个项目打的断点。
![[wIgBhjQGNl0nED4q-d9e4f485.webp]]
把之前打的断点取消掉。
![[IWUHIovcMLMOwIrZ-b6d7cd8f.webp]]
首先,从数据库中查是否已分配给用户秘钥,这代码逻辑应该在哪写呢?
之前我们的鉴权写在了模拟接口项目,用 idea 单独打开 yuapi-interface 项目。
![[xdcD92jbXOSYcuBQ-e1f325d2.webp]]
找到之前校验用户 API 签名的那段代码,这里我们是直接写在单个具体的方法里。
![[94beHi3htMAvZEy0-0a6825f6.webp]]
目前我们面临一个问题,就是项目尚未接入数据库,也并未引入 MyBatis。
我们不能指望开发人员自行引入 MyBatis 并调用我们的公共数据库,这显然是不切实际的。
因此,应该为开发人员提供一个远程调用的服务,以便帮助他们进行验证,或者我们可以提供一个 SDK 供他们引入,以便进行验证操作。在这种情况下,我们需要依赖一个公共的服务来实现这个功能。
### 3. 抽象公共服务(时间点 31:04-35:20)
**项目名:**yuapi-common
**目的:**让方法、实体类在多个项目间复用,减少重复编写。
**服务抽取:**
1. 数据库中查是否已分配给用户秘钥(根据 accessKey 拿到用户信息,返回用户信息,为空表示不存在)
2. 从数据库中查询模拟接口是否存在(请求路径、请求方法、请求参数,返回接口信息,为空表示不存在)
3. 接口调用次数 + 1 invokeCount(accessKey、secretKey(标识用户),请求接口路径)
**步骤:**
1. 新建干净的 maven 项目,只保留必要的公共依赖
2. 抽取 service 和实体类
3. install 本地 maven 包
4. 让服务提供者引入 common 包,测试是否正常运行
5. 让服务消费者引入 common 包
**进一步说明:**
接下来,我们计划创建一个公共服务模块,以便在多个项目之间实现方法和实体类的复用,从而避免重复编写相同的代码。
首先,我们需要明确哪些通用服务是必要的。先从数据库中查询是否已为用户分配了密钥,我们可以对这个方法进行进一步抽象,考虑需要传递哪些参数以及期望的返回值,具体来说,我们要查找数据库以确认是否已经将
accessKey 分配给了某个用户。
我们**需要关注哪些参数需要传递?**只需要 accessKey 参数,如果还有其他参数,可以在之后进行补充。
其次,我们需要处理的另一个问题是从数据库查询模拟接口是否存在。这个任务是网关模块的一部分,我们需要确保网关能够实现这个功能,具体来说,我们需要一个方法来检查数据库中是否存在特定的模拟接口。
**这个方法需要接收哪些参数呢?**找到网关项目 yuapi-gateway。
![[tPbldpMVUYhkWxS1-6e542bcc.webp]]
这里的第四步,我们既然是要查模拟接口是否存在,是不是要传递这个接口的信息。
![[hK0hquYomdIU7gFU-0490f618.webp]]
这**接口信息是什么?**是从请求路径和方法中获取到的,甚至你还可以传请求参数;
所以这里的话我们可以封装一个对象。
![[UfKAu8QPO80LSMdO-4ebf64d5.webp]]
第三个就是对接口调用次数进行加一操作。**这个过程需要传递什么参数呢?**
考虑到要将调用次数加一,必须要知道是哪个用户进行了接口调用,以及具体是哪个接口的调用次数要增加。在这里,可以借助 accessKey 和 secretKey
这两个密钥来标识用户并获取其信息。
因此,我们需要传递的参数包括 accessKey 和 secretKey。通过 secretKey,我们可以识别用户并获取相关信息。此外,还需要传递请求的接口路径。暂定这些,后续需要进行调整,也可以进行修改。
### 4. 创建公共服务(时间点 35:20-01:15:43)
#### 4.1 创建公共服务项目(时间点 35:20-40:36)
建一个 Maven 项目,提供公共的接口、实体类;
点击 File → New。
![[Cg6mvF8cj8jqyK9l-eeecbead.webp]]
创建 Maven 项目,选择 1.8 版本,点击 Next。
![[lKyH6NIiv3DzoWDt-90d21bab.webp]]
项目名`yuapi-common`。
![[VxTk4E4RVg6Kit2t-e0ba3b82.webp]]
选择在新窗口打开。
![[FF9mUrMA2om6jx3Z-2747b607.png]]
重新设置新项目的 Maven 仓库。
![[07ucyONbW1uikleM-f82291e7.webp]]
在 java 目录下新建包。
我们要将服务之间进行调用的几个方法写到公共类中,以及相应的 model,就看需要什么就传什么。
![[sRRsSchSY2qTqzaG-0f1ee744.webp]]
回到 yuapi-backend 项目,查看 invokeCount 方法,它这里接收接口的 id 和用户 id,我们只要拿到这两个即可。
![[idBXGZ8vNAettYTd-286c86cc.webp]]
我们就用它这个方法,复制 UserInterfaceInfoService.java,粘贴到 yuapi-common 项目中的 service
包下,还爆红了,因为实体类不存在,还要复制实体类。
![[Oro5KBK3zF4hl3B7-e040ac58.webp]]
复制 yuapi-backend 项目中的 model 包,粘贴到 yuapi-common 项目中。
![[nnIFwZz41QeV9CgL-0f04f8d2.webp]]
![[jMvn0OYAOHn3Rf2G-7c572d59.webp]]
把 dto 包删掉,因为它就是类与类之间的转换,不需要。
![[qGBCRtoMzHURQqkD-f79c0256.webp]]
在公共服务项目引入 yuapi-backend 项目的依赖,删除多余的依赖,这个公共服务只负责提供接口,不负责提供实现。
```xml
org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 com.baomidou mybatis-plus-boot-starter 3.5.1 com.google.code.gson gson 2.9.0 org.apache.commons commons-lang3 3.12.0 mysql mysql-connector-java runtime org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok true junit junit 4.13.2 test
```
粘贴到 pom.xml 中。
![[z6QRP94P7aTCrA0S-8fd67d70.webp]]
把 SpringBoot 的声明也粘贴进来,点击加载。
ps.这个声明相当于定义了一些依赖的版本、信息。
![[WEc7s3uDTLVM4bET-9da0a9f5.webp]]
![[HZ66B2alWuntnkhW-a1c88c18.webp]]
model 包下就不爆红了。
![[95R05JiXqLCJrNAO-1df55e71.png]]
来到 PostVO,删掉上面的引入,让它重新加载。
![[8MgGP6c5fl5NBPmm-d14ec96c.webp]]
![[mjePRxk9K7l5hYEV-ba5c7629.webp]]
回到 UserInterfaceInfoService 中,删掉上面的引入,让它重新加载。
![[bCE4cYvCqPEKRxMr-3b692def.webp]]
重新加载后就不爆红了。
![[zd3YFbrfmGoCiz8t-edf008bf.webp]]
#### 4.2 编写方法(时间点 40:36-54:17)
在 UserInterfaceInfoService 定义两个方法。
![[rDJuAJ8X2KPbtjVr-aafb1cbe.webp]]
我们不应该将所有的服务都集中在 UserInterfaceInfoService 中,应该将它们分成多个独立的服务进行编写;
复制 yuapi-backend 项目的 InterfaceInfoService.java、UserService.java。
![[HIfMo4GfVrL9Jmpz-96764a92.webp]]
粘贴到 yuapi-common 项目中。
![[AsFPkWvjeVr0nH8m-fd0ab139.webp]]
这两个文件还有爆红,删除引入,让它重新加载。
![[udoBgf4f0mBkywXW-5ee647ac.webp]]
![[CA173Gd64Bqu0cfl-ec8a8581.webp]]
![[RusmUpp05s7cKFpA-97ca7aa5.webp]]
![[xcGNbxSCHxvOi8kn-a39e1d51.webp]]
把 UserService 里面的用户注册、登录... 删掉,不需要。
![[WVPplDguJvZXWSdm-3622b42f.webp]]
来**写两个方法:**
1. 数据库中查是否已分配给用户秘钥(accessKey)
2. 从数据库中查询模拟接口是否存在(请求路径、请求方法、请求参数)
首先,在 UserService 中写数据库中查是否已分配给用户秘钥的方法。
![[F616PwhziYiN1VsK-44a75482.webp]]
其次,在 InterfaceInfoService 写从数据库中查询模拟接口是否存在的方法。
![[ZUHSkJasdYCcrw4z-685af170.webp]]
给以下两个文件统一加上`Inner`的文件名前缀。
![[vd0OzNAF3bHPh9nx-8cdf8f69.webp]]
![[z95VEXJZA1BFe7Or-e0ba21a6.png]]
删除多余的内容,没有继承它,修改后的三个文件:
![[DsN4mOY5p7RJdK1i-b0b617d9.webp]]
![[nfxI3VMpkcmdZFi3-3c9f49ab.webp]]
![[ZYzcvtbZCmIn67Tb-d279772b.webp]]
把 yuapi-common 项目打包,先设置它的版本为`0.0.1`。
![[iuVSsRsYgfjowi57-64eca481.webp]]
点击右侧 Maven,双击 install 进行打包。
![[iWDy6mYt7uKf2o3q-6dcab6c9.webp]]
打包成功。
![[8GiawG7ryfKKTwGs-ed8b3725.webp]]
#### 4.3 引入公共服务依赖(时间点 45:02-58:22)
复制公共服务版本信息。
![[R9gjm7009agKKClX-186cc007.webp]]
```xml
com.yupi yuapi-common 0.0.1
```
粘贴到 yuapi-backend 项目中。
![[N55QGQT6OsxPMTlR-62d9eaf6.webp]]
---
**💭** **题外话:**
在学习 Dubbo 的过程中,建议大家在学完 Spring Boot 和 Redis 之后开始学习。如果时间比较紧张,可以选择暂时不深入学习
Dubbo,但如果有半年或者更长时间来学习,建议在进入 Spring Cloud 之前先学习 Dubbo。这是因为 Spring Cloud
微服务架构的底层可能会使用 HTTP 协议或其他协议进行远程调用,微服务涉及到更多的知识领域,例如网关、分布式事务以及服务总线等多个概念。
而 Dubbo 作为一个 RPC 调用框架,更加专注于远程调用的领域,相对更为纯粹。因此,在掌握 Dubbo 的基础之后,再深入学习 Spring
Cloud 将会更有帮助。
---
验证一下,看看 yuapi-backend 项目能不能用这个公共的类了;
把 UserInterfaceInfoService.java、UserInterfaceInfo.java 删掉。
![[Iw8KY17IhDQiJCEA-1af499ee.webp]]
更换引入。
![[v6ceitsssiob7V3J-875f7270.webp]]
![[uSLFoQ58e6uqVlXc-5cafae1b.webp]]
![[dAFVx6bVDBvtRcXF-36d506aa.webp]]
![[7Gi7SvEm8ocjAVcr-716daa55.webp]]
![[OLcEUB4j1oEeFfUN-e029cbe8.webp]]
![[74TSuUnrzARtt5pp-e659e6c6.webp]]
![[yNAPkobhfiXfUqEt-43119142.webp]]
![[p2uNSJ2l3H1ChL4h-5f39f74b.webp]]
![[dsU1k7QcO3Cx26WS-87b38f9a.webp]]
![[aZhPwP4OycpnmcWc-c012970b.webp]]
![[z5WnUXRvtpqvJ12Z-43c8a3dc.webp]]
![[NDXlcOgw1RGubtl1-dec7394d.webp]]
![[6SnYAKRZ1btfRkd1-c0ed3eef.webp]]
![[ssbwcZFqPIcqunMF-9b8ab5a6.webp]]
![[q4fs5T9pe0B2Lia9-289e4655.webp]]
![[5Yq57m5t8Ngge6h8-7a85e438.webp]]
把这个 User、InterfaceInfo 删掉,直接调用公共服务里的。
![[AP6lAyncK6ebr4kD-23360cbe.webp]]
执行测试类,看看能不能运行成功。
![[HntVQnkeDUWKuOf2-dc56437e.webp]]
调用成功,也输出的 SQL 语句。
![[i5FULGkPSnc1BXNO-bdb35540.webp]]
现在公共服务算是抽取完成了。
#### 4.4 方法实现(时间点 58:22-01:15:43)
接下来就去实现刚刚的那几个方法,点这里,找一下 yuapi-common 依赖。
![[IHOWM28X3avnV40t-c9910047.webp]]
找到 InnerUserService,来实现接口;
光标放到 InnerUserService,按[Alt+Enter],选择`Implenment interface`(实现接口)。
![[BwOF6rT3VXtZKy6G-3c12047e.webp]]
选择目标目录。
![[klZwyabOsEQbcibJ-fa3fb577.webp]]
实现方法就选 getInvokeUser。
![[maGMgYBFnBny9v1J-7230e3a5.webp]]
它就生成在我们指定的目录下了。
![[w5LG4UbG43aBpI3J-7ac0a98f.webp]]
再实现 InnerInterfaceInfoService 的接口;
光标放到 InnerInterfaceInfoService,按[Alt+Enter],选择 Implenment interface。
![[yMiK7HvTVea9bQux-2c39a15f.webp]]
选择目标目录。
![[ksTt9OwCZ2okcBfd-bd85b2ae.webp]]
实现方法选 getInterfaceInfo。
![[vfEK5P7aMbmGIt2L-59d7e2c4.webp]]
生成好了。
![[wfFj7fsqjFQ5ilqA-d45019bc.webp]]
再生成另一个,光标放到 InnerUserInterfaceInfoService;
按[Alt+Enter],选择 Implenment interface。
![[xAvPbY5aiaY3x4zi-10261ead.webp]]
选择目标目录。
![[zgDInebhp3Stl5Lk-34dfaa81.webp]]
实现方法选 invokeCount。
![[HAo2ymYTdEDSdG7G-3a07a04f.webp]]
生成好了。
![[EzcY5MkNJGc6lmUd-0e548b16.webp]]
在实现类里,我们就可以编写对应的方法了。
先去写统计接口次数的这个类,直接引入 UserInterfaceInfoService,调用它的方法即可。
```java
package com.yupi.project.service.impl; import com.yupi.project.service.UserInterfaceInfoService; import com.yupi.yuapicommon.service.InnerUserInterfaceInfoService; import javax.annotation.Resource; public class InnerUserInterfaceInfoServiceImpl implements InnerUserInterfaceInfoService { @Resource private UserInterfaceInfoService userInterfaceInfoService; @Override public boolean invokeCount(long interfaceInfoId, long userId) { // 调用注入的 UserInterfaceInfoService 的 invokeCount 方法 return userInterfaceInfoService.invokeCount(interfaceInfoId, userId); } }
```
![[7kMwoBtgysEnwWSm-3d0a63d4.webp]]
再去写下获取调用用户信息的类,直接去数据库中根据 ak、sk 判断获取。
![[F1AJx8fw1StI223k-328a4dd7.webp]]
```java
package com.yupi.project.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.yupi.project.common.ErrorCode; import com.yupi.project.exception.BusinessException; import com.yupi.project.mapper.UserMapper; import com.yupi.yuapicommon.model.entity.User; import com.yupi.yuapicommon.service.InnerUserService; import org.apache.commons.lang3.StringUtils; import javax.annotation.Resource; public class InnerUserServiceImpl implements InnerUserService { @Resource private UserMapper userMapper; /** * 实现接口中的 getInvokeUser 方法,用于根据密钥获取内部用户信息。 * * @param accessKey 密钥 * @return 内部用户信息,如果找不到匹配的用户则返回 null * @throws BusinessException 参数错误时抛出业务异常 */ @Override public User getInvokeUser(String accessKey) { // 参数校验 if (StringUtils.isAnyBlank(accessKey)) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 创建查询条件包装器 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("accessKey", accessKey); // 使用 UserMapper 的 selectOne 方法查询用户信息 return userMapper.selectOne(queryWrapper); } }
```
![[ErEDnqMVWLTCEu9S-f90e9345.webp]]
最后写下根据请求路径、方法名获取接口信息的类。
![[eCTWq01loK0enpRU-3f79736b.webp]]
```java
package com.yupi.project.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.yupi.project.common.ErrorCode; import com.yupi.project.exception.BusinessException; import com.yupi.project.mapper.InterfaceInfoMapper; import com.yupi.yuapicommon.model.entity.InterfaceInfo; import com.yupi.yuapicommon.service.InnerInterfaceInfoService; import org.apache.commons.lang3.StringUtils; import javax.annotation.Resource; public class InnerInterfaceInfoServiceImpl implements InnerInterfaceInfoService { @Resource private InterfaceInfoMapper interfaceInfoMapper; /** * 实现接口中的 getInterfaceInfo 方法,用于根据URL和请求方法获取内部接口信息。 * * @param url 请求URL * @param method 请求方法 * @return 内部接口信息,如果找不到匹配的接口则返回 null * @throws BusinessException 参数错误时抛出业务异常 */ @Override public InterfaceInfo getInterfaceInfo(String url, String method) { // 参数校验 if (StringUtils.isAnyBlank(url, method)) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 创建查询条件包装器 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("url", url); queryWrapper.eq("method", method); // 使用 InterfaceInfoMapper 的 selectOne 方法查询接口信息 return interfaceInfoMapper.selectOne(queryWrapper); } }
```
![[sU9dNbPM7vgqEVOW-242ed6fb.webp]]
这三个接口就写完了~
大家还记得我们在**使用 Dubbo 时需要做哪些事情吗?**
参考下之前编写的`DemoService`,来看一下,它只需要在我们的实现类上添加`@DubboService`注解即可。
![[gnWE7pA7tF35zOD6-93eba736.webp]]
现在我们将刚刚的三个接口都加上这个注解,使它们也能够正常使用 Dubbo。
![[wqn09iWD4ZNIhQlA-83dbd4e8.webp]]
![[emIMACYONUnt0IVh-72e1f1ed.webp]]
![[6vYWyRea5dD6IMmm-21a68e86.webp]]
在 impl 包下新建`inner`包,把内部和外部都隔离开,将三个接口都放进去。
![[2cGE6EIhTdRdNy99-a38a0569.png]]
这些服务基本上写好了。
### 5. 调用公共服务(时间点 01:15:43-01:57:51)
#### 5.1 在网关编写调用公共服务(时间点 01:15:43-01:28:52)
以 debug 模式重启 yuapi-backend、yuapi-gateway 项目、启动 yuapi-interface 项目。
访问 nacos 注册中心 [http://自己服务的](http://xn--zfr16x5khr6p0nn/) IP
地址:8848/nacos/index.html,都注册上了。
![[wUKnsXXbM2ovFCfV-22cb0555.webp]]
如果发现 **yuapi-backend 项目启动失败:**
1. 网速问题,网速太卡了,导致注册超时,等一会,重新启动
2. 端口冲突,换个端口
ps.网速问题延伸,因为注册默认 3 秒,超过 3 秒即注册失败,可以去设置增加注册超时时间。
接下来就去网关项目中使用这些公共的服务,复制公共服务依赖。
![[C975OEVIBXbks7PL-f3ca8bd5.webp]]
```xml
com.yupi yuapi-common 0.0.1
```
粘贴到 yuapi-gateway 项目中。
![[0mPZICt5xBK29OJI-4130985a.webp]]
进入到网关要操作的主业务流程里,先**引入三个类:**
获取用户是否存在、获取接口信息是否存在、统计接口调用次数。
![[d39DbHXlWFPArzbc-07427d1a.webp]]
完成这步。
![[Bc8NYAa0y2RlVMsh-2a4c0bef.webp]]
修改后:
![[7tDwlPc3ao5Ce6vz-4b4a00ae.webp]]
往下滑,完善这步。
![[1TcCKGKVw35LPt67-862bbe7a.webp]]
修改后:
![[xZHdzD3lXFGc23fH-beec1e75.webp]]
往下滑,来整这步。
![[LH1E0LomuMcoNPsV-cf5fea4e.webp]]
先提取下请求路径、方法。
![[jvAxjHIExpotsnP6-733d7730.webp]]
然后修改:
![[phTsVPsOgsJlokTF-3ea8b469.webp]]
---
💡 **有同学提到了一种防止绕过网关的方法,即在网关上添加一个请求头。**
实际上,是否添加这个请求头并不是特别重要,之前已经和大家讨论过,我们的目标是为了实现流量染色。
如果要添加这个请求头,可以在请求中再添加一个固定的标识,例如"yuapi",并给它一个任意的值,然后在客户端进行判断。
但是目前考虑到实际影响不大,暂时不添加这个请求头了。因为如果添加的话,客户端还需要进行额外的判断,在每个模拟接口处添加逻辑或者编写一个公共方法,这会变得比较繁琐。
---
往下滑,来整这步。
![[3FVIV0UG2i9XC66D-0ddb2a89.webp]]
先添加两个参数:接口 id、用户 id。
![[AeDjvlIfdLWUNO3b-0fdb9a5e.webp]]
然后去修改:
![[pIvQeoRIb4qUxgXz-4f57d50f.webp]]
ps.在生产环境中,通常公司都会接一个报警系统,以确保一旦出现问题,能够立即触发报警机制。
#### 5.2 调用测试(时间点 01:28:52-01:57:51)
重启 yuapi-gateway 项目,有小报错没关系,能显示之前的示例结果即可。
![[u1q91vwbacyVK802-e999b5e2.webp]]
打个断点。
![[efenbzfCzb13YiBV-5fc1bda0.webp]]
访问 [http://localhost:8090/api/name/get?name=yupi,自动跳回](http://localhost:8090/api/name/get?name=yupi%EF%BC%8C%E8%87%AA%E5%8A%A8%E8%B7%B3%E5%9B%9E) yuapi-gataway
项目。
![[5306UCUpJp9bXUVi-dc0865a0.png]]
按[F8]执行到这里,你会发现它的请求路径是 /api/name/get。
![[RacFqncIXl1vjum8-13f52ab5.webp]]
按[F9]结束执行,这个有点问题,我们后端数据库存的是 [http://localhost:7529/name/user。](http://localhost:7529/name/user%E3%80%82)
![[dlBWLlchzr082EdT-5ca9289c.webp]]
---
**临时问题:如何获取接口转发服务器的地址**
**思路提供:**网关启动时,获取所有的接口信息,维护到内存的 hashmap 中;有请求时,根据请求的 url 路径或者其他参数(比如 host
请求头)来判断应该转发到哪台服务器、以及用于校验接口是否存在。
这里的参数比如说,在客户端调用接口时使用。你需要获取特定接口的信息,例如其中的主机(host)信息。在客户端发起请求时,你可以将这个主机信息添加到请求头中。这样,网关就能够获取到这个请求头。
**进一步说明:**
就是根据用户请求的地址,我们要找出需要转发的目标服务器地址。为了获取类似于 localhost:7529 这样的地址,我们需要进行一些步骤,**如何实现这一步骤呢?**建议从数据库中获取这些信息。这里提供一个参考方法,但值得注意的是,这一步不是必需的,可以根据个人情况决定是否采取这一步。
**具体方法如下:**当我们启动网关项目时,首先从数据库中获取所有接口信息。对于每个 URL,我们需要获取与之关联的路径以及接口名称信息。之后,根据接口的其他相关信息,我们可以找到应该转发到的目标主机(host)。可以为数据库添加一个额外的字段,用于存储转发的目标服务器地址,通过这个地址,我们可以将请求转发到对应的路径上,这就是大致的流程。
---
偷个懒,因为我们所有的模拟接口项目都位于同一个地址(8123),直接将这个地址进行拼接。
通常情况下,如果你自己在开发类似的模拟接口平台,也可以像这里一样,事先写入几个固定的地址,避免每次都需要从数据库中读取的繁琐过程。
写一个地址的常量,然后将其拼接。
![[HXuBTTcDVHe2e1sO-3072f3f5.webp]]
重启 yuapi-gateway 项目,打开 yuapi-client-sdk 项目;
这个项目也要去引入公共服务。
![[A7K2IsXKPFvrwSch-5fbbe219.webp]]
---
💡 有同学问:**如何让其他用户上传自己编写的接口?**
需要提供一个注册机制。在这个机制下,其他用户可以上传他们自己编写的接口信息。为了简化流程,可以设计一个用户友好的界面。在这个界面上,用户可以输入他们的接口信息,包括服务器地址(host)、请求路径等内容。也可以规定,在接入我们的平台时,用户必须使用我们提供的
SDK 或遵循一定的要求。
**如何进行接入和要求的遵循?**在用户上传接口的时候,我们需要对接口信息进行测试调用,以确保接口的正常运行,这可以通过我们的平台来完成。同时,我们也可以要求用户标明该接口是否是由我们的网关调用,这可能需要用户在代码中加入判断代码,或者引入我们提供的
SDK 来实现。
**接口信息的组织和存储:**当用户上传接口信息时,这些信息将被存储在 InterfaceInfo 接口中。除了 URL 外,还应该添加一个
host 字段,用于明确区分不同服务器的地址。这样,可以更清晰地区分请求路径和服务器地址,提高接口信息的可读性和可维护性。
---
我们回到前端去调用,不在浏览器调用了,这样的话密码也绕不过去。
启动前端项目,访问 [http://localhost:8000,登录,就调用第一个接口,点击`查看`。](https://www.codefather.cn/course/1790979723916521474/section/1790987424616722433?type=#heading-0http://localhost:8000%EF%BC%8C%E7%99%BB%E5%BD%95%EF%BC%8C%E5%B0%B1%E8%B0%83%E7%94%A8%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%8E%A5%E5%8F%A3%EF%BC%8C%E7%82%B9%E5%87%BB%60%E6%9F%A5%E7%9C%8B%60%E3%80%82)
![[Uv1bH4unu5DgG6rY-9c2a12da.webp]]
输入请求参数后,点击调用。
```json
{"username":"yupi"}
```
![[MnyLMRzzOc7CLaEf-b16535ef.webp]]
自动跳回 yuapi-gateway 项目。
![[t0qImr9UtzAisGAZ-85e83943.webp]]
按[F8]向下执行到这里,请求来源是 127.0.0.1。
![[RB6hQdJOK7JF2ON8-55b8c388.webp]]
按[F8]向下执行到这里,参数都拿到了。
![[4Zdi89bZSCqaA9SE-a9e69095.webp]]
再往下,就是远程调用了,在 yuapi-backend 项目打个断点。
![[GnWB7PvmTipaMtf2-f40991f3.webp]]
回到 yuapi-gateway 项目按[F8],就会跳到我们刚刚在 yuapi-backend 项目打断点的地方。
![[hkhXnSMwV1Adtm9y-2f0e9a1a.webp]]
按[F8]跳到下一行,再按[F9]结束执行,然后把 yuapi-backend 项目断点取消。
![[Dih44UnUvi46qaGC-523aa554.webp]]
回到 yuapi-backend 继续按[F8]向下执行。
![[RP0sTvlkrHyO5emM-2ad14508.webp]]
执行到这里,发现它显示没有查到,按[F9]结束执行。
![[IsD0ydvljzmeIeUe-683dfd02.webp]]
把这里的断点取消。
![[EGBr621kcqDAF1Ug-5dee1481.webp]]
往下滑,在这里打个断点。
![[lO01SQ6BNZiwTxTP-9bdd5daf.webp]]
去数据库人工修改下 url 的数据为 [http://localhost:8123/api/name/user,先把整个流程跑通。](http://localhost:8123/api/name/user%EF%BC%8C%E5%85%88%E6%8A%8A%E6%95%B4%E4%B8%AA%E6%B5%81%E7%A8%8B%E8%B7%91%E9%80%9A%E3%80%82)
![[pw0pIR3oLF7XC2zt-4ce22e90.webp]]
不过啊,因为我们登录的是 yupi 账号,如果登录其他账号就会报无权限错误,为什么呢?
因为我们之前的接口逻辑是写在模拟接口项目里的,我们现在的校验逻辑不应该写在这里;
注释掉,重启模拟接口项目。
![[4POf3TzEbl1txdVC-3d41680d.webp]]
在前端重新点击`调用`,自动跳转回 yuapi-gateway 项目。
![[x4uUdiQHmoeAxdOi-31a5edd2.webp]]
这次拿到了,按[F9]结束执行。
![[79ITe1oUidWT8zly-b3db7ec3.webp]]
返回前端页面查看,成功输出。
![[ikSU0ATYYRuYqwHb-b0bba5d7.webp]]
去数据库中查,也能看见次数的变化,现在整个业务流程跑通。
![[MGAm8b3tXJVZqdCv-1ff96f49.webp]]
ps.还有个小问题,我们的网关没有校验用户是否还有调用次数,这个一定要放在发送请求前,交给大家实现🐶。
![[h1z74UjBER53inS1-f514c9f8.webp]]
我们回顾一下之前的业务流程,看一下用户是如何获取到他们的 AKSK 用于模拟调用接口的。
找到 InterfaceInfoController 中的 invokeInterfaceInfo 方法。
那么在这个方法中,我们是**如何获取 AKSK 的呢?**
首先,用户需要进行登录,我们就能从登录用户的信息中提取出他们的 AKSK。因此,可以说我们之前已经实现了动态的操作,能够在运行时获取用户信息并为他们分配密钥了。
![[QfUUMsIr7hUIm5HV-e3a7d7b4.webp]]
所以 application.yml 这里写什么都无所谓了。
![[eMsf4xgqmuEECjN2-96db0597.webp]]
## 五、统计分析功能(时间点 01:57:51-02:51:38)
接下来再给大家讲一个小的知识点,我们要去开发专门供管理员使用的统计分析功能,在实际开发过程中,并不需要去开发这个功能,根据实际需求而定。
### 1. 需求分析(时间点 01:59:19-02:02:39)
各接口的总调用次数占比(饼图)取调用最多的前 3 个接口,从而分析出哪些接口没有人用(降低资源、或者下线),高频接口(增加资源、提高收费)。
**进一步说明:**
**现在我们可以进行哪些分析呢?**基于我们当前的业务和数据,我们可以分析系统中的用户注册情况,即每日新增用户数量或总用户数,或者是,哪些接口被频繁调用、对于同一用户,他们使用的接口情况,例如某个用户今天调用了多少次接口。
**我们要做什么呢?**我们基于现有的数据,假设一些需求。例如,设想统计某个特定用户对接口的调用次数占比,可以采用饼图来呈现这个数据,通过图表,能直观地看到接口被调用的次数。
然而,在着手处理这个需求之前,我们必须要明确分析的目的是什么。我们一般进行分析都是为了达到某个特定的目标,就拿🐟的情况举例,之前主要从事运营分析和大数据分析,对诉求就是要求很高,所以我们必须要明确清楚分析诉求。
回到我们刚才提到的需求,以此为例,我们可以将其分解一下。这个需求的目的明确地是让某个用户,或者说用户本人,能够了解在某段时间内使用哪些接口的次数较多。**这样的分析有何意义呢?**
可以据此判断某个接口在特定时间内是否被频繁调用,如果某个接口在某段时间内几乎没有调用记录,那么**我们是否需要继续保持资源的分配呢?**另一方面,对于高频次调用的接口,我们可以考虑是否需要增加相应资源,或者在某些情况下进行收费等等。因此,这个需求背后是有很多的价值和意义的。
### 2. 后端开发(时间点 02:02:39-02:21:46)
#### 2.1 SQL 查询调用数据(时间点 02:02:39-02:07:26)
接下来,我们需要考虑如何实际实现这个需求,实现起来并不复杂。
这个实现涉及前后端的协同沟通,后端的任务相对简单,主要是涉及到数据库增删改查的操作,我们需要编写一个接口来获取示例数据,比如:“接口 A 被调用了两次,接口
B 被调用了三次”。
那么,**这个接口应该如何编写呢?**我们**需要获取哪些接口信息呢?**比如接口的名称、描述、地址等等。但是考虑到接口数量可能会很多,不可能全部展示出来,所以可以设置一个条件,只取前三个接口作为示例。首先,我们需要获取到每个接口的
interfaceInfoId,因为我们要按接口进行分组统计,这个统计操作涉及到了 user\_interface\_info 表。
让我们着手编写相应的 SQL 查询语句。基本上,我们需要按接口进行分组,然后统计每组接口的调用总数,这可以通过以下 SQL 查询实现:
```sql
-- 获取接口调用次数的统计信息,并按照调用总次数降序排列,最后取前三个接口作为结果 select interfaceInfoId, sum(totalNum) as totalNum from user_interface_info group by interfaceInfoId order by totalNum desc limit 3;
```
接下来,进行测试,先往数据库中添加一些数据,模拟不同接口被不同用户调用的情况,然后运行这个查询语句,查看结果是否符合预期。
![[FhNcKDT4jhTbyOnw-83e5d2c6.webp]]
---
💡 **有同学说:有必要增加热点推荐功能吗?**
个人认为没有必要,我们不必过于复杂化这个系统,如果我们的接口数量很少,甚至可以在一个屏幕上展示出来,那么就没有必要考虑热点推荐功能。
---
打开 SQL 控制台。
![[X14RVREB7Q5nP73W-7071d841.webp]]
![[koJdm1HraAL4q0RN-87b2f5d4.png]]
将 SQL 语句粘贴进去,按[Ctrl+A]全选,点击绿色按钮执行。
![[fHVTlusgKY8Ch5nj-82283c36.webp]]
下方显示查询结果。
![[cVoBx8mgXx25XhJf-4fe70405.webp]]
#### 2.2 业务层去关联查询接口信息(时间点 02:07:26-02:21:46)
在获取了这个 SQL 查询结果之后,**还有其他需要处理的事情吗?**
现在我们只拿到了接口的 id,但如果除了 id,还需要展示接口的名称,**应该如何处理呢?**
需要进行关联查询来获取接口信息。由于我们只取了前三,所以在这种情况下进行关联查询不会对性能产生太大影响,如果大家要展示所有接口,关联查询可能就会变得复杂。
回到 yuapi-backend 项目,新建一个 controller:AnalysisController。
![[Xy3RtTjYK4Pupkqh-ff413bbd.webp]]
先写一点内容。
![[OVjGkp1IPuKZBJ14-71c7f2eb.webp]]
再新建一个包装类,复制 PostVO.java,粘贴到 vo 包下,并重命名为`InterfaceInfoVO`。
![[bCNR40XpuLXMsn7T-978567d0.png]]
然后修改一下 InterfaceInfoVO.java。
```java
package com.yupi.project.model.vo; import com.yupi.yuapicommon.model.entity.InterfaceInfo; import lombok.Data; import lombok.EqualsAndHashCode; /** * 接口信息封装视图 * * @author yupi * @TableName product */ @EqualsAndHashCode(callSuper = true) @Data // 这里就继承InterfaceInfo,再补充一个调用次数的字段 public class InterfaceInfoVO extends InterfaceInfo { /** * 调用次数 */ private Integer totalNum; private static final long serialVersionUID = 1L; }
```
![[bw5wDEg2sCTi9hHL-88b64bd6.webp]]
由于我们需要查询,涉及到两个表,user\_interface\_info 表和接口信息表。因此,在这种情况下,我们可以在映射层(Map层)编写一个自定义的
SQL 查询语句,然后在业务层实现下面的关联查询。
找到 UserInterfaceInfoMapper 整个自定义 SQL,把刚刚编写的 SQL 语句粘贴进去。
![[XVOkzGESphxcKZkA-b3d97e8a.webp]]
编写代码。
![[LQMy4hl8FeHBNjao-483470aa.webp]]
把光标放到 listTopInvokeInterfaceInfo 中,按[Alt+Enter] 生成 statement。
![[lJdmfmCenycsZvGH-be785f89.webp]]
自动生成了对应的 map。
![[eQwliUQXgLqdHwUm-d1e228c3.webp]]
把 SQL 语句粘贴过来。
![[pdgnTRP5ql837Bla-a32db085.webp]]
![[ehom1LpFnShbic3i-ac1e33ae.webp]]
我们是**如何处理这个业务的呢?**我们并不是直接在代码中编写 SQL,而是先将 SQL 写好,然后在数据库中执行它,确保 SQL 的正确性。然后再开始编写代码,这样可以节省很多时间。
如果你遇到业务逻辑问题,怀疑是数据查询的问题,可以先打印相关日志,并将 SQL 语句复制到数据库中执行一次。这样可以确定是业务代码问题还是
SQL 语句问题。
然后修改一下,把 limit 改成动态参数。
![[1bHKW3OdQH7SZtnM-1ffbe0c5.webp]]
继续编写 AnalysisController。
```java
package com.yupi.project.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.yupi.project.annotation.AuthCheck; import com.yupi.project.common.BaseResponse; import com.yupi.project.common.ErrorCode; import com.yupi.project.common.ResultUtils; import com.yupi.project.exception.BusinessException; import com.yupi.project.mapper.UserInterfaceInfoMapper; import com.yupi.project.model.vo.InterfaceInfoVO; import com.yupi.project.service.InterfaceInfoService; import com.yupi.yuapicommon.model.entity.InterfaceInfo; import com.yupi.yuapicommon.model.entity.UserInterfaceInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.context.annotation.Configuration; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 分析控制器 */ @RestController @RequestMapping("/analysis") @Slf4j public class AnalysisController { @Resource private UserInterfaceInfoMapper userInterfaceInfoMapper; @Resource private InterfaceInfoService interfaceInfoService; /** * 获取调用次数最多的接口信息列表。 * 通过用户接口信息表查询调用次数最多的接口ID,再关联查询接口详细信息。 * * @return 接口信息列表,包含调用次数最多的接口信息 */ @GetMapping("/top/interface/invoke") @AuthCheck(mustRole = "admin") public BaseResponse> listTopInvokeInterfaceInfo() { // 查询调用次数最多的接口信息列表 List userInterfaceInfoList = userInterfaceInfoMapper.listTopInvokeInterfaceInfo(3); // 将接口信息按照接口ID分组,便于关联查询 Map> interfaceInfoIdObjMap = userInterfaceInfoList.stream() .collect(Collectors.groupingBy(UserInterfaceInfo::getInterfaceInfoId)); // 创建查询接口信息的条件包装器 QueryWrapper queryWrapper = new QueryWrapper<>(); // 设置查询条件,使用接口信息ID在接口信息映射中的键集合进行条件匹配 queryWrapper.in("id", interfaceInfoIdObjMap.keySet()); // 调用接口信息服务的list方法,传入条件包装器,获取符合条件的接口信息列表 List list = interfaceInfoService.list(queryWrapper); // 判断查询结果是否为空 if (CollectionUtils.isEmpty(list)) { throw new BusinessException(ErrorCode.SYSTEM_ERROR); } // 构建接口信息VO列表,使用流式处理将接口信息映射为接口信息VO对象,并加入列表中 List interfaceInfoVOList = list.stream().map(interfaceInfo -> { // 创建一个新的接口信息VO对象 InterfaceInfoVO interfaceInfoVO = new InterfaceInfoVO(); // 将接口信息复制到接口信息VO对象中 BeanUtils.copyProperties(interfaceInfo, interfaceInfoVO); // 从接口信息ID对应的映射中获取调用次数 int totalNum = interfaceInfoIdObjMap.get(interfaceInfo.getId()).get(0).getTotalNum(); // 将调用次数设置到接口信息VO对象中 interfaceInfoVO.setTotalNum(totalNum); // 返回构建好的接口信息VO对象 return interfaceInfoVO; }).collect(Collectors.toList()); // 返回处理结果 return ResultUtils.success(interfaceInfoVOList); } }
```
重启 yuapi-backend 项目,后端整完了~
### 3. 前端开发(时间点 02:02:39-02:51:38)
#### 3.1 图表库(时间点 02:02:39-02:23:14)
接下来我们要着手处理前端部分,我们的目标是在前端页面上展示一个饼图。
那么,**如何展示饼图呢?**一般来说,我们并不需要自己手动绘制饼图,因为已经有现成的图表库可供使用。在大多数情况下,我们会使用现成的图表库,除非你的公司拥有充足的资源来开发自己的图表组件。比如:
- [ECharts](https://echarts.apache.org/zh/index.html)(推荐)
- [AntV](https://antv.vision/zh)(推荐)
- BizCharts
ECharts 唯一的缺点是它的外观相对较传统,不够现代化,缺乏一些科技感和精致度,但它的功能却非常强大。
BizCharts 则是一个商业性质的图表库,提供的图表更加华丽。另外,AntV 也是一个值得推荐的选择,相较于 BizCharts,AntV
更被广泛推崇。
**如何使用图表库:**
1. 看官网
2. 找到快速入门、按文档去引入库
3. 进入示例页面
4. 找到你要的图
5. 在线调试
6. 复制代码
7. 改为真实数据
如果是 React 项目,用这个库:[https://github.com/hustcc/echarts-for-react。](https://github.com/hustcc/echarts-for-react%E3%80%82)
#### 3.2 AntV 官网浏览(时间点 02:23:14-02:24:15)
访问 AntV 官网 —— [AntV](https://antv.vision/zh)。
![[IaAkcBNaKZUqq5M0-2c9e47e5.webp]]
AntV 是阿里蚂蚁金服开发的一套数据可视化库,用于数据呈现与展示。大家以后需要进行数据可视化,强烈建议使用 AntV。这个库拥有丰富的功能和一系列的产品,其界面设计也相当出色。
AntV 不仅仅是一款数据可视化工具,它还包括了多个子库。
其中,G2 专注于 PC 端图表的展示与绘制;
![[BiDCvC78vY8NUfEn-ec140c3a.webp]]
S2 则提供了多维表格的功能,适用于专注于表格数据的场景;
![[AZwER3mc3DcLKnjV-bb74a67a.webp]]
G6 则用于处理关系型数据的可视化,尤其适用于图论和模型分析等领域;
![[ueeo1rkI5UpwBmu5-2e921cf2.webp]]
而 X6 则是一套流程编辑引擎,可用于创建流程图和 ER 图等,非常适合构建流程图系统。
![[x2dvOb2LxS2VwUyB-ca8089cd.webp]]
L7 是更高级的数据可视化库,它专注于地理位置城市化、空间城市化以及地理数据的可视化呈现,这使得它在展示地理信息方面具有非常出色的表现。
![[6OT7h2xzU11yQ8Ik-aaa75574.webp]]
F2 则专注于移动端图表的展示与绘制。与之对应的,G2则是适用于 PC 端的图表库,两者在不同终端上都能够提供高效的数据可视化。
![[V3KQ7EOCF5YQgrR0-ed2cef0f.webp]]
AVA 并不是针对普通开发人员的工具。它更适用于对框架进行深度二次开发,或者研发自己的技术框架,需要进行更加复杂的数据分析、定制化数据分析等等。一般来说,大多数开发者并不需要使用
AVA。
![[ZWq4pN71OJ9yczWt-864d3abf.webp]]
接下来,对于我们的 PC 端,可能会使用 G2 这个库。如何使用呢?后面可能还会给大家更详细地介绍,但本期先给大家快速地看一下,其实所有这些可视化库都有相似的用法:
进入官方网站,然后浏览网站上的图表示例。你可以只专注于图表示例,无需浏览其他内容,直接找到你需要的图表样式。
![[m50ZPgeJyvSXXD8r-fc3e4575.webp]]
找到饼图,点击进入。 —— [饼图](https://g2.antv.antgroup.com/examples/general/pie/#pie)
![[RynZ5HZFuLhr3O6l-40e2064e.webp]]
右侧有现成的代码供你使用,无需过多考虑,只需要将代码复制粘贴到的项目中。
接着,将后端实际获取到的数据替换掉原始代码中获取数据的部分,这里就不介绍这个库的使用方法,因为它的用法非常简单。所有这些可视化图表库的用法都类似,它们没有太大的学习成本,除非你要深入研究这些内容。
![[G843E0GZlkW0Z7Ei-df7d719e.webp]]
#### 3.3 ECharts 使用(时间点 02:24:15-02:33:54)
访问 ECharts 官网,点击`示例`。 —— [ECharts](https://echarts.apache.org/zh/index.html)
ps.百度已将 ECharts 捐赠给了 Apache 基金会。
![[EzyGI6n8hCzDLW4v-ba84bb13.webp]]
找到 [饼图](https://echarts.apache.org/examples/zh/editor.html?c=pie-simple),就用第一个。
![[Y4q87hEwj4x2yhjb-2ca92c93.webp]]
可以在这里调试。
![[hnsNEpdr2wGwvBcQ-e3d24350.webp]]
比如这里改成 '鱼皮'。
![[mZXgPuDeTq6EbRd3-892ff6cf.webp]]
然后复制代码。
```java
option = { title: { text: 'Referer of a Website', subtext: 'Fake Data', left: 'center' }, tooltip: { trigger: 'item' }, legend: { orient: 'vertical', left: 'left' }, series: [ { name: 'Access From', type: 'pie', radius: '50%', data: [ { value: 1048, name: '鱼皮' }, { value: 735, name: 'Direct' }, { value: 580, name: 'Email' }, { value: 484, name: 'Union Ads' }, { value: 300, name: 'Video Ads' } ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] };
```
代码先留着,接下来要引入 Echarts。
因为我们前端项目是 react,去 [github](https://github.com/) 找 react 版的,搜索:
[react-echarts](https://github.com/hustcc/echarts-for-react)。
![[9J926oDuohctgj0y-c96f7426.webp]]
![[fFE9aTBgA4KHZev7-c595207d.webp]]
往下滑,复制安装命令。
![[lf4OwM0PMuil7j92-a62f28db.webp]]
```bash
npm install --save echarts-for-react npm install --save echarts
```
粘贴到前端的终端中执行。
![[9Anov0YrqaplNy1P-2176fd10.webp]]
![[cM4J0i7ucvdmcqKE-757cde8a.webp]]
安装之后,回到 github 往下看,看它怎么用。
只需要将其引入,引入后,需要使用 ReactECharts 组件,这里有一个 option 选项。
![[1tuJTar6en3y2JW7-1ca0eb47.webp]]
```python
import ReactECharts from 'echarts-for-react'; // render echarts option.
```
option 选项对应我们示例代码中的这里。
![[iMQwH0THUtPGADfq-8b26455f.webp]]
#### 3.4 新建分析页(时间点 02:33:54-02:51:38)
去 routes.ts 增加新的路由。
![[PlVLQxpNuhQeP9Cr-5edd32f3.webp]]
在 Admin 目录下新建`InterfaceAnalysis`目录,复制 index.tsx,粘贴到新建的目录下。
![[p6Gx4MKXf2SZdZJR-cc3d607c.webp]]
删除 index.tsx 多余的内容,并修改一下。
![[PuJAZtwhqYTB49y7-4279f774.webp]]
引入 ECharts。
![[XsPQUeVSjxAtvSKk-70284893.webp]]
然后把示例代码粘贴进来,修改一下。
```java
import { PageContainer } from '@ant-design/pro-components'; import '@umijs/max'; import React, { useEffect, useState } from 'react'; import ReactECharts from 'echarts-for-react'; /** * 接口分析 * @constructor */ const InterfaceAnalysis: React.FC = () => { // 存储数据的状态 const [data, setData] = useState([]); // 控制加载状态的状态,默认加载中(true) const [loading, setLoading] = useState(true); useEffect(() => { // todo 从远程获取数据 },[]) // ECharts图表的配置选项 const option = { title: { text: 'Referer of a Website', subtext: 'Fake Data', left: 'center' }, tooltip: { trigger: 'item' }, legend: { orient: 'vertical', left: 'left' }, series: [ { name: 'Access From', type: 'pie', radius: '50%', data: [ { value: 1048, name: '鱼皮' }, { value: 735, name: 'Direct' }, { value: 580, name: 'Email' }, { value: 484, name: 'Union Ads' }, { value: 300, name: 'Video Ads' } ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] } return ( {/* 使用 ReactECharts 组件,传入图表配置 */} ); }; export default InterfaceAnalysis;
```
回到前端页面查看效果,成功展示出来了。
![[lXFreX9hFvE3LoCv-56fcde3a.webp]]
我们刚刚在后端新写了一个接口,在前端用 openapi 生成。
![[v9OPoNBvVlk5ulZd-f02b7ca2.webp]]
继续编写分析页的代码。
```java
import { PageContainer } from '@ant-design/pro-components'; import '@umijs/max'; import React, {useEffect, useState} from 'react'; import ReactECharts from 'echarts-for-react'; import {listTopInvokeInterfaceInfoUsingGET} from "@/services/yuapi-backend/analysisController"; /** * 接口分析 * @constructor */ const InterfaceAnalysis: React.FC = () => { const [data, setData] = useState([]); useEffect(() => { try { listTopInvokeInterfaceInfoUsingGET().then(res => { if (res.data) { setData(res.data); } }) } catch (e: any) { } // todo 从远程获取数据 }, []) // 映射:{ value: 1048, name: 'Search Engine' }, const chartData = data.map(item => { return { value: item.totalNum, name: item.name, } }) const option = { title: { text: '调用次数最多的接口TOP3', left: 'center', }, tooltip: { trigger: 'item', }, legend: { orient: 'vertical', left: 'left', }, series: [ { name: 'Access From', type: 'pie', radius: '50%', data: chartData, emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)', }, }, }, ], }; return ( ); }; export default InterfaceAnalysis;
```
回到前端页面查看效果。
![[pOkoEM9ewAuO4EkW-b7d1acc1.webp]]
如果要改样式,先在 ECharts 调整好,再粘贴到项目中即可。
![[hnsNEpdr2wGwvBcQ-e3d24350.webp]]
开发完咯~ 下期不再见<( ̄ˇ ̄)/
## 六、上线计划(时间点 02:51:38-03:03:28)
**前端:**参考之前用户中心或伙伴匹配系统的上线方式。
**后端:**
- backend 项目:web 项目,部署 spring boot 的 jar 包(对外的)
- gateway 网关项目:web 项目,部署 spring boot 的 jar 包(对外的)
- interface 模拟接口项目:web 项目,部署 spring boot 的 jar 包(不建议对外暴露的)
**关键:网络必须要连通**
**自己学习用:**单个服务器部署这三个项目就足够。
如果你是搞大事,多个服务器建议在 **同一内网** ,内网交互会更快、且更安全。
## 七、扩展思路
1. **用户可以申请更换签名**
2. **怎么让其他用户也上传接口?**
- 需要提供一个机制(界面),让用户输入自己的接口 host(服务器地址)、接口信息,将接口信息写入数据库。
- 可以在 interfaceInfo 表里加个 host 字段,区分服务器地址,让接口提供者更灵活地接入系统。
- 将接口信息写入数据库之前,要对接口进行校验(比如检查他的地址是否遵循规则,测试调用),保证他是正常的。
- 将接口信息写入数据库之前遵循咱们的要求(并且使用咱们的 sdk),
- 在接入时,平台需要测试调用这个接口,保证他是正常的。
1. **网关校验是否还有调用次数**
- 需要考虑并发问题,防止瞬间调用超额。
1. **网关优化**
- 比如增加限流 / 降级保护,提高性能等。还可以考虑搭配 Nginx 网关使用。
1. **功能增强**
- 可以针对不同的请求头或者接口类型来设计前端界面和表单,便于用户调用,获得更好的体验。
- 可以参考 swagger、postman、knife4j 的页面。
上面提到的要检查用户提供的地址是否符合规则。我们的项目中,所有的模拟接口都是以 /api 开头,或者说,我们规定所有模拟接口的地址都必须以
[http://localhost:8123](http://localhost:8123/)开头。你可以根据需要自行设置规则,然而,在制定这些规则时,最好不要过于严格,最好像之前提到的,可以在数据库中存储一个
host 的信息,只需满足一些特定的后缀条件,比如以 /api 开头等。
这个项目实际上有很多可以发展的方向,有许多要考虑的要点。如果你希望项目取得成功,就需要考虑许多方面。你需要制定规范,关注安全性、性能等方面。此外,还需要考虑如何防止被滥用。
---
**有同学问:Dubbo 不是需要暴露服务才可以吗?**
我们这里不是那个模拟接口用 Dubbo 去暴露的,**我们暴露的是什么?**暴露是系统内部用的接口,什么查询数据库中是否给用户分配密钥、接调用接口次数统计。暴露的不是开发者提供的接口,开发者提供的接我们是通过网关去转发的。然后这里你如果想让网端直接通过同一套地址去调用,那你就让用户遵循这个地址规则。
**有同学问:现在的 SDK 不是固定了方法吗?**
对于这个问题,需要明确一个**核心点:**一旦接口投入使用后,肯定要针对这个接口进行 SDK 的开发。你需要不断地完善 SDK,使其适应不断变化的需求。当然,也可以让
SDK 从接口信息表中读取信息,然后动态生成方法等等,这种做法也是可行的,然而,并不建议这样做。实际上,接口的发布可能不太像我们在应用商店发布应用那样灵活,在这种接口的发布过程中,建议还是介入一点人工。
以腾讯云的 SDK 手册为例,它有许多不同的 SDK,尽管代码可能是自动生成的,但是它的 SDK 是根据接口动态变化的。这意味着它并不是为
100 个接口提供同一个代码供调用,相反,它会为每个方法、每个接口生成相应的方法,同样,它也会根据不同的地址生成相应的方法。这一点非常重要,请大家多去使用一下第三方的
API 接口,这样就会更了解对方的 SDK 是如何设计的。
你可以去腾讯云下载他们的 SDK,会发现其中包含大量方法,基本上每个接口都有相应的方法。这样做的目的是让使用者更加便利,他们只需要输入方法名就能实现操作,无需关心具体的接口地址。
**有同学问:就是每次发布一个接口,都需要更新一次 SDK?**
是的,实际上每次发布一个新的接口,都需要对 SDK 进行更新。**建议的做法**是这样的:由于用户并不关心具体的接口地址,因此你可以让他们直接调用方法名,然后根据这些方法名去动态生成对应的方法。每次发布新接口时,更新
SDK 的操作可以做得非常简单,可以采用脚本的方式,从数据库中读取接口地址,与之前已有的地址进行对比,然后补充相应的方法,这个过程是可行的,事实上,很多公司都在这样做。
---
**项目分区导航**:⬅️ [[08-项目笔记(一)|08-项目笔记(一)]] | 09-项目笔记(七) - API 开放项目项目教程 - 编程导航教程 | ➡️ [[10-项目笔记(三)|10-项目笔记(三)]]